#! /usr/bin/env node
import packageJson from './package.json'
import { program } from 'commander'
import fs from 'fs-extra'
import path, { dirname } from 'path'
import inquirer from 'inquirer'
import chalk from 'chalk'
import ora from 'ora'
import ejs from 'ejs'
import { renderTemplate } from './utils/renderTemplate'
import { preOrderDirectoryTraverse } from './utils/directoryTraverse'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

interface Options {
  force?: boolean
}

// 目标目录可能存在，就要提醒用户是否进行删除
// 同时要配合用户的强制删除options，用户的配置优先级更高
const processTargetDirectory = async (name: string, options: Options) => {
  return new Promise(async (resolve) => {
    const cwd = process.cwd()
    const target = path.join(cwd, name)
    let force = options.force
    const isExistTarget = fs.existsSync(target)
    // 有目录，无force配置时候才询问用户
    if (isExistTarget && typeof options.force === 'undefined') {
      // 询问用户
      const answer = await inquirer.prompt<{ force: boolean }>([
        {
          type: 'confirm',
          name: 'force',
          message: `Do you want to overwrite directory ${name}`,
          default: false,
        },
      ])
      force = answer.force
    }
    // 有目录，不能强制覆盖
    if (isExistTarget && !force) {
      console.log(chalk.red('✘') + ' ' + `The directory ${name} already exists`)
      process.exit(0)
    }
    // 有目录，可以强制覆盖
    if (isExistTarget && force) {
      const spinner = ora(`${name} is deleting`)
      spinner.start()
      fs.remove(target, (err) => {
        if (err) {
          console.error(err)
          process.exit(0)
        }
        spinner.succeed(`delete ${name} success`)
        return resolve(true)
      })
    } else {
      return resolve(true)
    }
  })
}

const FEATURE_OPTIONS = [
  {
    name: 'TypeScript',
    value: 'typescript',
  },
  {
    name: 'JSX 支持',
    value: 'jsx',
  },
  {
    name: 'Router（单页面应用开发）',
    value: 'router',
  },
  {
    name: 'Pinia（状态管理）',
    value: 'pinia',
  },
  {
    name: 'Vitest（单元测试）',
    value: 'vitest',
  },
  {
    name: 'ESLint（错误预防）',
    value: 'eslint',
  },
  {
    name: 'Prettier（代码格式化）',
    value: 'prettier',
  },
] as const
type Feature = (typeof FEATURE_OPTIONS)[number]['value']

const inquireConfig = async () => {
  const answer = await inquirer.prompt<{ features: Feature[] }>([
    {
      type: 'checkbox',
      name: 'features',
      message: 'Please select the features',
      choices: FEATURE_OPTIONS,
    },
  ])
  return answer
}

const create = async (name: string, options: Options) => {
  // 项目目录预处理
  await processTargetDirectory(name, options)

  // 询问用户需要的配置
  const { features } = await inquireConfig()
  const needsTypeScript = features.includes('typescript')
  const needsJsx = features.includes('jsx')
  const needsPrettier = features.includes('prettier')
  const needsRouter = features.includes('router')
  const needsPinia = features.includes('pinia')
  const needsVitest = features.includes('vitest')
  const needsEslint = features.includes('eslint')

  // 创建目录
  const targetDir = name
  const cwd = process.cwd()
  const root = path.join(cwd, targetDir)
  fs.mkdirSync(root)

  // 生成基础的package.json文件
  const pkgJson = { name: name, version: '0.0.0' } // 写入package.json的name和版本
  fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkgJson, null, 2)) // 缩进为2

  // 模板文件位置
  const templateRoot = path.resolve(__dirname, 'template')
  const callbacks: Function[] = []
  const render = function (templateName) {
    const templateDir = path.resolve(templateRoot, templateName)
    renderTemplate(templateDir, root, callbacks)
  }

  // 渲染基础项目
  render('base')

  // 添加对应的配置
  if (needsJsx) {
    render('config/jsx')
  }

  if (needsPrettier) {
    render('config/prettier')
  }

  if (needsPinia) {
    render('config/pinia')
  }

  if (needsRouter) {
    render('config/router')
  }

  if (needsVitest) {
    render('config/vitest')
  }

  if (needsEslint) {
    render('config/eslint')
    render('eslint/base')
    if (needsTypeScript) {
      render('eslint/typescript')
    }
    if (needsVitest) {
      render('eslint/vitest')
    }
    if (needsPrettier) {
      render('eslint/prettier')
    }
  }

  if (needsTypeScript) {
    render('config/typescript/base')
    // tsconfig
    render('tsconfig/base')
    const rootTsConfig = {
      files: [],
      references: [{ path: './tsconfig.node.json' }, { path: './tsconfig.app.json' }],
    }
    if (needsVitest) {
      render('tsconfig/vitest')
      rootTsConfig.references.push({
        path: './tsconfig.vitest.json',
      })
    }
    fs.writeFileSync(
      path.resolve(root, 'tsconfig.json'),
      JSON.stringify(rootTsConfig, null, 2) + '\n',
      { encoding: 'utf-8' },
    )
  }

  // 添加入口文件
  render('entry/default')
  if (needsPinia) {
    render('entry/pinia')
  }
  if (needsRouter) {
    render('entry/router')
  }

  // 添加code
  // 有router和没有router，会影响到code的结构
  // 有ts和没有ts，也会影响到code的代码
  // 所以这里区分为四个模板，进行渲染
  const codeTemplate = (needsTypeScript ? 'typescript-' : '') + (needsRouter ? 'router' : 'default')
  render(`code/${codeTemplate}`)

  // 收集所有的ejs的数据
  const dataStore = {}
  for (const cb of callbacks) {
    await cb(dataStore)
  }
  // 根据ejs数据渲染对应的模板文件
  preOrderDirectoryTraverse(
    root,
    () => {},
    (filePath) => {
      if (filePath.endsWith('.ejs')) {
        const template = fs.readFileSync(filePath, { encoding: 'utf-8' })
        const dest = filePath.replace(/\.ejs$/, '')
        const content = ejs.render(template, dataStore[dest])
        fs.writeFileSync(dest, content)
        fs.unlinkSync(filePath)
      }
    },
  )

  // ts代码是js的超集，对于生成的代码中，还需要对ts进行转换处理，如下转换逻辑
  // 如果有冗余的xxx.ts和xxx.js，直接清理掉xxx.js文件。也就是说，如果生成的代码里面有两种文件，默认选用ts文件覆盖js文件
  // 如果只有js文件，这种文件视为改一下文件名，就可以当作ts文件使用的。因为ts是js的超集
  // jsconfig.json需要删除
  // 相关的js的引用也要改为ts的引用
  if (needsTypeScript) {
    preOrderDirectoryTraverse(
      root,
      () => {},
      (filePath) => {
        if (filePath.endsWith('.js')) {
          const tsFilePath = filePath.replace(/\.js$/, '.ts')
          if (fs.existsSync(tsFilePath)) {
            fs.unlinkSync(filePath)
          } else {
            fs.renameSync(filePath, tsFilePath)
          }
        } else if (filePath.endsWith('.mjs')) {
          const tsFilePath = filePath.replace(/\.mjs$/, '.mts')
          if (fs.existsSync(tsFilePath)) {
            fs.unlinkSync(filePath)
          } else {
            fs.renameSync(filePath, tsFilePath)
          }
        } else if (path.basename(filePath) === 'jsconfig.json') {
          fs.unlinkSync(filePath)
        }
      },
    )

    // index.html里面的js引用更改
    const indexHtmlPath = path.resolve(root, 'index.html')
    const indexHtmlContent = fs.readFileSync(indexHtmlPath, { encoding: 'utf-8' })
    fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace('src/main.js', 'src/main.ts'))
  } else {
    // 一般来说，有ts配置才会生成有ts文件，但是这里做一下冗余处理，删除掉所有ts文件
    preOrderDirectoryTraverse(
      root,
      () => {},
      (filepath) => {
        if (filepath.endsWith('.ts')) {
          fs.unlinkSync(filepath)
        }
      },
    )
  }
}

program
  .name(packageJson.name)
  .description('cli to create a project of vue3')
  .version(packageJson.version)
program
  .command('create')
  .description('create a new project of vue3')
  .argument('<string>', 'project name')
  .option('-f, --force', 'overwrite target directory if it already exists')
  .action((name: string, options: Options) => {
    create(name, options)
  })
program.parse(process.argv)
